import sys
import math
import cv2
import numpy as np

# argv[1]
out_mp4_path = sys.argv[1]
# argv[2]
out_bin_path = sys.argv[2]
# argv[3]
vout_bin_path = sys.argv[3]


#
# # argv[1]
# out_mp4_path = 'out.MOV'
# # argv[2]
# out_bin_path = 'out.bin'
# # argv[3]
# vout_bin_path = 'vout.bin'

def Prepocessing(img):
    # 灰度化
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    cv2.imshow("1", img)
    # 平滑化    需要调参
    # img = cv2.blur(img,(3,3))
    # cv2.imshow("2",img)
    # 图像增强  需要调参
    cv2.convertScaleAbs(img, img, alpha=1.5, beta=10)
    img = cv2.equalizeHist(img)
    cv2.imshow("3", img)
    # 阈值化处理：（设置阈值根据实际情况 如视图中已找不到特征 可适量调整）
    cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU, img)
    cv2.imshow("4", img)

    cv2.waitKey(0)
    cv2.destroyAllWindows()


# 规定参数
myfilesize = 200000  # bytes
mywidth = 400
myheight = 225
myunitsize = 8300
myfps = 10

mythickness = 5
myexpandedrate = 6
pic_width = (mywidth + 2 * mythickness) * myexpandedrate
pic_height = (myheight + 2 * mythickness) * myexpandedrate

bigwe = 2
pSize = 15
aSize = 15
smallwe = 2


# 480 -> unitsize = 12000
# 400 -> unitsize = 8300
# 320 -> unitsize=5200
# 160 ->1250


def compute_1(contours, i, j):
    '''最外面的轮廓和子轮廓的比例'''
    area1 = cv2.contourArea(contours[i])
    area2 = cv2.contourArea(contours[j])
    if area2 == 0:
        return False
    ratio = area1 * 1.0 / area2
    if abs(ratio - 49.0 / 25):
        return True
    return False


def compute_2(contours, i, j):
    '''子轮廓和子子轮廓的比例'''
    area1 = cv2.contourArea(contours[i])
    area2 = cv2.contourArea(contours[j])
    if area2 == 0:
        return False
    ratio = area1 * 1.0 / area2
    if abs(ratio - 25.0 / 9):
        return True
    return False


def compute_center(contours, i):
    '''计算轮廓中心点'''
    M = cv2.moments(contours[i])
    cx = int(M['m10'] / M['m00'])
    cy = int(M['m01'] / M['m00'])
    return cx, cy


def detect_contours(vec):
    '''判断这个轮廓和它的子轮廓以及子子轮廓的中心的间距是否足够小'''
    distance_1 = np.sqrt((vec[0] - vec[2]) ** 2 + (vec[1] - vec[3]) ** 2)
    distance_2 = np.sqrt((vec[0] - vec[4]) ** 2 + (vec[1] - vec[5]) ** 2)
    distance_3 = np.sqrt((vec[2] - vec[4]) ** 2 + (vec[3] - vec[5]) ** 2)
    if sum((distance_1, distance_2, distance_3)) / 3 < 3:
        return True
    return False


def sort_4points(box):
    new_box = sorted(box, key=lambda x: x[1])
    if new_box[0][0] > new_box[1][0]:
        new_box[0], new_box[1] = new_box[1], new_box[0]

    if new_box[2][0] < new_box[3][0]:
        new_box[2], new_box[3] = new_box[3], new_box[2]
    return new_box


def sort_get(rec, a, b, c, d):
    new_rec = []
    new_rec.append(rec[a])
    new_rec.append(rec[b])
    new_rec.append(rec[c])
    new_rec.append(rec[d])
    x_sort = sorted(new_rec, key=lambda x: x[0])
    # y_sort = sorted(new_rec, key = lambda x:x[1])
    if x_sort[0][1] < x_sort[1][1]:
        l_u = x_sort[0]
        l_d = x_sort[1]
    else:
        l_u = x_sort[1]
        l_d = x_sort[0]
    if x_sort[2][1] < x_sort[3][1]:
        r_u = x_sort[2]
        r_d = x_sort[3]
    else:
        r_u = x_sort[3]
        r_d = x_sort[2]
    return l_u, r_u, l_d, r_d


def judge_cube(rec):
    if len(rec) < 3:
        return -1, -1, -1, -1
    for i in range(len(rec)):
        for j in range(i + 1, len(rec)):
            for k in range(j + 1, len(rec)):
                for l in range(k + 1, len(rec)):
                    l_u, r_u, l_d, r_d = sort_get(rec, i, j, k, l)
                distance_1 = np.sqrt((l_u[0] - r_u[0]) ** 2 + (l_u[1] - r_u[1]) ** 2)
                distance_2 = np.sqrt((l_u[0] - l_d[0]) ** 2 + (l_u[1] - l_d[1]) ** 2)
            distance_3 = np.sqrt((r_d[0] - l_d[0]) ** 2 + (r_d[1] - l_d[1]) ** 2)
            distance_4 = np.sqrt((r_u[0] - r_d[0]) ** 2 + (r_u[1] - r_d[1]) ** 2)
        if abs(distance_1 - distance_3) < 100:
            if abs(distance_2 - distance_4) < 100:
                return l_u, r_u, l_d, r_d

    print("Failed")
    return -1, -1, -1, -1


def find(image, contours, hierachy, root=0):
    rec = []
    for i in range(len(hierachy)):
        child = hierachy[i][2]
        child_child = hierachy[child][2]
        if child != -1 and child_child != -1:
            if compute_1(contours, i, child) and compute_2(contours, child, child_child):
                cx1, cy1 = compute_center(contours, i)
                cx2, cy2 = compute_center(contours, child)
                cx3, cy3 = compute_center(contours, child_child)
                if detect_contours([cx1, cy1, cx2, cy2, cx3, cy3]):
                    rec.append([cx1, cy1, cx2, cy2, cx3, cy3, i, child, child_child])
    # 计算轮廓符合标准的中心点
    # print(len(rec))
    # print(rec)

    l_u, r_u, l_d, r_d = judge_cube(rec)
    if l_u == -1 and r_u == -1 and l_d == -1 and r_d == -1:
        return

    cubel_u = cv2.minAreaRect(contours[l_u[6]])
    cubel_u = cv2.boxPoints(cubel_u)
    cuber_u = cv2.minAreaRect(contours[r_u[6]])
    cuber_u = cv2.boxPoints(cuber_u)
    cubel_d = cv2.minAreaRect(contours[l_d[6]])
    cubel_d = cv2.boxPoints(cubel_d)
    cuber_d = cv2.minAreaRect(contours[r_d[6]])
    cuber_d = cv2.boxPoints(cuber_d)

    cubel_u = sort_4points(cubel_u)
    cuber_u = sort_4points(cuber_u)
    cubel_d = sort_4points(cubel_d)
    cuber_d = sort_4points(cuber_d)

    point1 = cubel_u[0]
    point2 = cuber_u[1]
    point3 = cubel_d[3]
    point4 = cuber_d[2]

    # debug:
    # print(l_u[0],l_u[1])
    # print(r_u[0],r_u[1])
    # print(l_d[0],l_d[1])
    # print(r_d[0],r_d[1])

    w_width = mywidth * myexpandedrate
    w_height = myheight * myexpandedrate

    pts1 = np.float32([point1,
                       point2,
                       point3,
                       point4])  # 所需图像部分四个顶点的像素点坐标
    pts2 = np.float32([[0, 0],
                       [w_width, 0],
                       [0, w_height],
                       [w_width, w_height]])  # 定义对应的像素点坐标
    matrix_K = cv2.getPerspectiveTransform(pts1, pts2)  # 使用getPerspectiveTransform()得到转换矩阵
    image = cv2.warpPerspective(image, matrix_K, (w_width, w_height))  # 使用warpPerspective()进行透视变换

    return image


def get_area(image):
    # image = cv2.resize(image, (1000, int(1000 * height / width)))
    # preprocess:
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    _, gray = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)
    # findcontours:
    contours, hierachy = cv2.findContours(gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    #   findcontours参数含义：
    # contours，定义为“vector<vector<Point>> contours”，是一个向量，并且是一个双重向量，向量内每个元素保存了一组由连续的Point点构成的点的集合的向量
    # hierachy，分别表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号，当无对应轮廓时：取默认值-1
    # （向量hiararchy内的元素和轮廓向量contours内的元素是一一对应的）
    image = find(gray, contours, np.squeeze(hierachy))

    return image


r = 0
dataLen = 0
HammingLen = 0


def GetLenForValid(width, height, big_size, small_size):
    total_length = height * width - 3 * (big_size) ** 2 - (small_size) ** 2
    # total_length - k <= 2**k - 1
    k = math.ceil(math.log(total_length + 1, 2))
    while True:
        if 2 ** k + k < total_length + 1:
            k += 1
            break
        k -= 1
    return k


# 求长度的位数


def get_length(length_bit, result):  # 求长度
    length = 0
    for i in range(0, length_bit):
        length = length + int(result[i]) * math.pow(2, length_bit - 1 - i)  # 求长度
    return length


# 求长度


def chooseHammingN(n):
    global r
    global dataLen
    global HammingLen
    dataLen = n
    r = 0
    while pow(2, r) - r < dataLen + 1:
        r += 1
    HammingLen = r + dataLen  # 求出插入校验码后的总长


def hammingDecode(HammingData):
    global r
    global dataLen
    global HammingLen
    wrongList = []
    for pn in range(1, r + 1):  # 逐个计算校验组
        # pn所在位置
        pos = pow(2, (pn - 1)) - 1
        temp = 0
        for pr in range(pos, HammingLen + 1, pow(2, pn)):
            for i in range(pr, min(pr + pow(2, (pn - 1)), HammingLen)):  # 疯狂异或
                temp ^= HammingData[i]
        if temp == 1:
            wrongList.append(pn)  # 记下哪个校验组错了
    if wrongList:
        if len(wrongList) == 1:
            HammingData[pow(2, wrongList[0] - 1) - 1] ^= 1
        elif len(wrongList) == 2:
            if 1 in wrongList and 2 in wrongList:
                HammingData[2] ^= 1
            elif 1 in wrongList and 3 in wrongList:
                HammingData[4] ^= 1
            elif 1 in wrongList and 4 in wrongList:
                HammingData[8] ^= 1
            elif 2 in wrongList and 3 in wrongList:
                HammingData[5] ^= 1
            elif 2 in wrongList and 4 in wrongList:
                HammingData[9] ^= 1
            elif 4 in wrongList and 3 in wrongList:
                HammingData[11] ^= 1
        elif len(wrongList) == 3:
            if 1 in wrongList and 2 in wrongList and 3 in wrongList:
                HammingData[6] ^= 1
            elif 1 in wrongList and 2 in wrongList and 4 in wrongList:
                HammingData[10] ^= 1
    return HammingData


# 海明码解码

def extractData(encoded):
    chouQv = []
    for i in range(len(encoded)):
        skip = False
        for j in range(5):
            if 2 ** j > len(encoded):
                continue
            if 2 ** j - 1 == i:
                skip = True
        if not skip:
            chouQv.append(encoded[i])
    return chouQv


def str_to_bytes(s):
    leng = len(s)
    int_array = []
    for i in range(int(leng / 8)):
        x = int(s[i * 8:i * 8 + 8], 2)
        int_array.append(x)
    if leng % 8 != 0:
        x = int(s[int(leng / 8) * 8:leng] + '0' * (8 - leng % 8), 2)
        int_array.append(x)
    byte_data = bytes(int_array)
    return byte_data


def Decode(width, height, big_size, small_size, image, n, now_length):
    chooseHammingN(n)
    image = cv2.resize(image, (width, height))  # 图片压缩
    # mask_code=np.zeros((width,height))#掩码
    binary_code = np.array(image)  # 二进制编码
    for i in range(0, height):
        for j in range(0, width):
            if binary_code[i][j] > 126:
                binary_code[i][j] = 0
            else:
                binary_code[i][j] = 1  # 二进制编码初始化
    #     for i in range(0, width):
    #         for j in range(0, height):
    #             if ((i * j) % 3 + (i + j) % 2) % 2 == 0:
    #                 mask_code[i][j] = 1
    # # 掩码赋值
    limited = np.zeros((height, width))  # 部分区域限制
    for i in range(0, big_size):
        for j in range(0, big_size):
            limited[i][j] = 1  # 左上角大方块
    for i in range(0, big_size):
        for j in range(width - big_size, width):
            limited[i][j] = 1  # 右上角大方块
    for i in range(height - big_size, height):
        for j in range(0, big_size):
            limited[i][j] = 1  # 左下角大方块
    for i in range(height - small_size, height):
        for j in range(width - small_size, width):
            limited[i][j] = 1  # small_size*small_size小方框
    #     for i in range(0, width):
    #         for j in range(0, height):
    #             if limited[i][j] == 0 and mask_code[i][j] == 1:
    #                 if binary_code[i][j] == 1:
    #                     binary_code[i][j] = 0
    #                 else:
    #                     binary_code[i][j] = 1
    # # 解掩码
    result = ""
    result_list = []
    mod = (width - 1) % 4
    for j in range(width - 1, 0, -2):
        if j % 4 == mod:
            for i in range(height - 1, -1, -1):
                if limited[i][j] == 0:
                    result += str(binary_code[i][j])
                if limited[i][j - 1] == 0:
                    result += str(binary_code[i][j - 1])
        else:
            for i in range(0, height):
                if limited[i][j] == 0:
                    result += str(binary_code[i][j])
                if limited[i][j - 1] == 0:
                    result += str(binary_code[i][j - 1])
        if mod == 2:
            for i in range(0, height):
                if limited[i][0] == 0:
                    result += str(binary_code[i][0])
        elif mod == 0:
            for i in range(height - 1, -1, -1):
                if limited[i][0] == 0:
                    result += str(binary_code[i][0])
    # 解码并且将内容填入result
    result_list = list(result)
    result_temp = ""
    for i in range(len(result) // (16 + r)):
        result_list = list(result[i * (16 + r):(i + 1) * (16 + r)])
        for x in range(len(result_list)):
            result_list[x] = int(result_list[x])  # 转换海明码的格式
        result_list = hammingDecode(result_list)
        result_list = extractData(result_list)
        for x in range(len(result_list)):
            result_list[x] = str(result_list[x])
        result_temp += "".join(result_list)
    result_list = list(result[len(result) // (16 + r) * (16 + r)::])
    result = result_temp
    length_bit = GetLenForValid(width, height, big_size, small_size)  # 求长度的位数
    # length = get_length(length_bit, result)  # 求长度
    number = int(get_length(8, result))
    now_length *= 8
    final_result = ""
    for i in range(length_bit + 8, now_length + 8 + length_bit):
        final_result += result[i]
    bytes_data = str_to_bytes(final_result)
    return bytes_data, number


# 解码 path：图片路径 return :str->只包含0101数据的字符串
'''
width -> 图片的像素宽（对应编码部分的width）
height ->图片的像素高(对于编码部分的height)
big_size->对应解码中的 pSize+bigwe
samll_size->对应解码中的 aSize+smallwe
path-> 图片的路径
n-> 海明码的分组
返回值：bytes
--------------------------------
解码部分输入的图片无需压缩，函数内部有压缩的功能
其中n越小纠错能力越弱，数据的占比量越大
'''

# parameter
original_fps = myfps  # 生成视频的fps
k = 0.7
#
# filepath = 'D:/test/out/pics/'
#


# load video from your path
vc = cv2.VideoCapture(out_mp4_path)
fp = open(out_bin_path, 'wb')

if vc.isOpened():  # 判断是否正常打开
    rval, frame = vc.read()
else:
    rval = False

fps = vc.get(5)
print("fps:" + str(fps))
timeRate = (1 / (original_fps)) * k  # 期望的截取的时间间隔
frameRate = int(timeRate * int(fps))  # 截取的帧间隔
if (frameRate < 1):
    print("Error: frameRate is lower than 1!")
actual_timeRate = frameRate / fps  # 实际的截取的时间间隔：由于frameRate一定要取整数，所以实际时间间隔可能不等于期望时间间隔
# 一张图片最大的被截取次数：生成视频的停留时间 / 在截取时实际的截取间隔 +1
max_cap_times = (1 / original_fps) / actual_timeRate + 1
print("frameRate:" + str(frameRate))
print("actual timeRate:" + str(actual_timeRate))
print("Max captured times:" + str(max_cap_times) + "\n")

start = False
start2 = False
end = False

pic_count = 0
pic_num = myfilesize // myunitsize
last_pic = myfilesize % myunitsize
if last_pic != 0:
    pic_num += 1

print("pic num: " + str(pic_num))
print("unit size of last pic: " + str(last_pic))

c = 0
while rval:  # 循环读取视频帧
    rval, frame = vc.read()
    if not rval:
        print("Finished")
        break
    if (c % frameRate == 0):
        image = frame
        # cv2.imwrite(filepath+str(c)+"-ori.png",frame)
        image = get_area(image)
        # print("debug: successfullly get area")
        # cv2.imwrite(filepath+str(c)+".png",image)

        if not start2:
            codeinfo, no = Decode(width=mywidth, height=myheight, big_size=pSize + bigwe, small_size=aSize + smallwe,
                                  image=image, n=16, now_length=7)
            if codeinfo == b'<start>' and no == 0:
                start = True
            elif start and no == 1:
                start2 = True
                codeinfo, no = Decode(width=mywidth, height=myheight, big_size=pSize + bigwe,
                                      small_size=aSize + smallwe, image=image, n=16, now_length=myunitsize)
                print("No.1" + "-------" + str(no) + ":" + str(codeinfo))
                pic_count = 1
                fp.write(codeinfo)
            print(str(no) + ":" + str(codeinfo))

        else:
            if pic_count < pic_num - 1:
                size = myunitsize
            elif pic_count == pic_num - 1:
                size = last_pic
                end = True
            else:
                break
            codeinfo, no = Decode(width=mywidth, height=myheight, big_size=pSize + bigwe, small_size=aSize + smallwe,
                                  image=image, n=16, now_length=size)
            print(str(no) + ":" + str(codeinfo))

            if no == pic_count + 1:
                pic_count += 1
                # debug
                print("No." + str(pic_count) + "---------" + str(no) + ":" + str(codeinfo))
                fp.write(codeinfo)
            elif no < pic_count + 1 or no > pic_num - 1 or no > pic_count + 2:
                c = c + 1
                continue
            else:
                tempinfo = bytes([0])
                for i in range(size - 1):
                    tempinfo += bytes([0])
                for i in range(no - pic_count - 1):
                    fp.write(tempinfo)
                pic_count = no
                print("No." + str(pic_count) + "---------" + str(no) + ":" + str(codeinfo))
                fp.write(codeinfo)

        if end:
            print("the number of pics: " + str(pic_count))
            break
    c = c + 1
    cv2.waitKey(1)

fp.close()
vc.release()

with open(out_mp4_path, 'rb') as f1, open(out_bin_path, 'rb') as f2, open(
        vout_bin_path, 'wb') as f3:
    byte1 = f1.read(1)
    byte2 = f2.read(1)
    while byte1 and byte2:
        tmp = [(byte1[0] ^ byte2[0])]
        bytearray_obj = bytearray(tmp)
        for i in range(len(bytearray_obj)):
            bytearray_obj[i] = ~bytearray_obj[i] & 0xff  # 按位取反

        result_byte = bytes(bytearray_obj)
        # result_byte = bytes([~(byte1[0] ^ byte2[0])])
        # 将异或结果写入新文件
        f3.write(result_byte)
        byte1 = f1.read(1)
        byte2 = f2.read(1)

# read the file & save it to binary_data
# open the file in binary
f = open(out_bin_path, "rb")

f.seek(0, 2)
eof = f.tell()
f.seek(0, 0)

unit_size = myunitsize
count = 1
# it indicates the number of bytes in a unit
# so that each qrcode has the same size
while True:
    unit_data = f.read(unit_size)
    # debug & display
    print(str(count) + ": " + str(unit_data))
    count += 1
    if f.tell() >= eof:
        break

f.close()

# parameters:
unit_size = 8300

f_input = open(out_mp4_path, 'rb')
f_output = open(out_bin_path, 'rb')
f_input.seek(0, 2)
eof_input = f_input.tell()
f_input.seek(0, 0)
f_output.seek(0, 2)
eof_output = f_output.tell()
f_output.seek(0, 0)

# 以byte为单位比较
total = 0
error = 0
error_list = []  # error_list包括错误字节所在的信息单元序号+该byte在该单元中的序号
unit_count = 0  # 一个信息单元的第几个byte：0代表最后一个，1代表第一个

while True:
    b_input = f_input.read(1)
    b_output = f_output.read(1)
    unit_count = (unit_count + 1) % unit_size
    total = total + 1
    if b_input != b_output:
        # 得到出错位,并得到出错所在的信息单元序号
        temp = total
        if (temp % unit_size == 0):
            temp = int(temp / unit_size)
        else:
            temp = int(temp / unit_size) + 1
        error_list.append([temp, unit_count])

        error = error + 1

    if f_input.tell() >= eof_input:
        if f_output.tell() < eof_output:
            # if output data is more than input data
            print("output data overflow!")
        break
    elif f_output.tell() >= eof_output:
        # if input data more than output: consider excessive data as wrong bytes
        cur_p = f_input.tell()
        f_input.seek(0, 2)
        total = total + (f_input.tell() - cur_p)
        break

ac = float(total - error) / float(total)

print("Total number of translated bytes:" + str(total))
print("Accuracy:" + str(ac))
for each in error_list:
    print("\t" + str(each[0]) + ":\t" + str(each[1]))

# 记录错误信息
with open("argv[3].txt", 'w') as f:
    f.write("Total number of translated bytes:\t" + str(total))
    f.write("Accuracy:\t" + str(ac))
    f.write("\n")

f_input.close()
f_output.close()
